import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import fetchHeaderVulnerabilityQuery from 'ee/security_dashboard/graphql/header_vulnerability.graphql';
import VulnerabilityFooter from 'ee/vulnerabilities/components/footer.vue';
import VulnerabilityHeader from 'ee/vulnerabilities/components/header.vue';
import Vulnerability from 'ee/vulnerabilities/components/vulnerability.vue';
import Details from 'ee/vulnerabilities/components/vulnerability_details.vue';
import { stubComponent } from 'helpers/stub_component';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { createAlert } from '~/alert';

Vue.use(VueApollo);
jest.mock('~/alert');

describe('Vulnerability', () => {
  let fetchHeaderVulnerabilityQuerySpy;
  let wrapper;

  const getVulnerability = (props) => ({
    id: 1,
    created_at: new Date().toISOString(),
    report_type: 'sast',
    state: 'detected',
    create_mr_url: '/create_mr_url',
    new_issue_url: '/new_issue_url',
    project_fingerprint: 'abc123',
    pipeline: {
      id: 2,
      created_at: new Date().toISOString(),
      url: 'pipeline_url',
      sourceBranch: 'main',
    },
    description: 'description',
    identifiers: 'identifiers',
    links: 'links',
    location: 'location',
    name: 'name',
    project: {
      full_path: '/project_full_path',
      full_name: 'Test Project',
    },
    discussions_url: '/discussion_url',
    notes_url: '/notes_url',
    can_modify_related_issues: false,
    related_issues_help_path: '/help_path',
    merge_request_feedback: null,
    issue_feedback: null,
    remediation: null,
    ...props,
  });

  const createApolloProvider = (...queries) => {
    return createMockApollo([...queries]);
  };

  const createApolloProviderForVulnerabilityStateChange = (mockFn = jest.fn()) => {
    fetchHeaderVulnerabilityQuerySpy = mockFn.mockResolvedValue({
      data: {
        errors: [],
        vulnerability: {
          id: 'gid://gitlab/Vulnerability/54',
          resolvedAt: '2020-09-16T11:13:26Z',
          state: 'RESOLVED',
        },
      },
    });

    return createApolloProvider([fetchHeaderVulnerabilityQuery, fetchHeaderVulnerabilityQuerySpy]);
  };

  const createWrapper = ({ vulnData, apolloProvider } = {}) => {
    wrapper = shallowMount(Vulnerability, {
      apolloProvider,
      propsData: {
        initialVulnerability: { ...getVulnerability(), ...vulnData },
      },
      stubs: {
        VulnerabilityFooter: stubComponent(VulnerabilityFooter),
      },
    });
  };

  afterEach(() => {
    createAlert.mockReset();
  });

  const findHeader = () => wrapper.findComponent(VulnerabilityHeader);
  const findDetails = () => wrapper.findComponent(Details);
  const findFooter = () => wrapper.findComponent(VulnerabilityFooter);

  describe('default behavior', () => {
    beforeEach(() => {
      createWrapper({ apolloProvider: createApolloProviderForVulnerabilityStateChange() });
    });

    it('consists of header, details, and footer', () => {
      expect(findHeader().exists()).toBe(true);
      expect(findDetails().exists()).toBe(true);
      expect(findFooter().exists()).toBe(true);
    });

    it('passes the correct properties to the children', () => {
      const vulnerability = getVulnerability();
      expect(findHeader().props('vulnerability')).toEqual(vulnerability);
      expect(findDetails().props('vulnerability')).toEqual(vulnerability);
      expect(findFooter().props('vulnerability')).toEqual(vulnerability);
    });
  });

  describe('vulnerability state change event', () => {
    let refreshVulnerability;

    beforeEach(() => {
      refreshVulnerability = jest.fn();
      createWrapper({
        apolloProvider: createApolloProviderForVulnerabilityStateChange(refreshVulnerability),
      });
    });

    it('re-fetches the query when the footer emitted a state-change', async () => {
      expect(refreshVulnerability).toHaveBeenCalledTimes(0);

      findFooter().vm.$emit('vulnerability-state-change');
      await waitForPromises();

      expect(refreshVulnerability).toHaveBeenCalledTimes(1);
    });

    it('passes the updated vulnerability to the footer when the header emitted a state-change', async () => {
      const updatedVulnerability = getVulnerability({ state: 'dismissed' });
      findHeader().vm.$emit('vulnerability-state-change', updatedVulnerability);
      await waitForPromises();

      expect(findFooter().props('vulnerability')).toEqual(updatedVulnerability);
    });
  });

  describe('refresh vulnerability', () => {
    describe('on failure', () => {
      beforeEach(() => {
        const apolloProvider = createApolloProvider([
          fetchHeaderVulnerabilityQuery,
          jest.fn().mockRejectedValue({
            data: {
              errors: [{ message: 'something went wrong while fetching the vulnerability' }],
              vulnerability: null,
            },
          }),
        ]);

        createWrapper({ apolloProvider });
      });

      it('calls createAlert', async () => {
        findFooter().vm.$emit('vulnerability-state-change');
        await waitForPromises();
        expect(createAlert).toHaveBeenCalledTimes(1);
      });
    });
  });
});
